﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Reflection;
using Cubicle.Core.Shared;
using Cubicle.Excel.Binding;
using Cubicle.Excel.Config;
using Cubicle.Excel.Functions;
using Cubicle.Excel.Hosting;
using Cubicle.Excel.Shared;
using ExcelDna.Integration;

namespace Cubicle.Excel.AddIn
{
    public class CubicleAddIn : IExcelAddIn
    {
        // Nlog
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        // Global variables
        public static EngineManager Engines;
        public static CompositionContainer Container;
        public static BindManager Binder;
        public static ExcelConfig Config;

        public const string EmbeddedAlias = "default";
        private const string _autoRunPath = "autorun";
        private const string _autoBindPath = "autobind";

        #region Implementation of IExcelAddIn

        public void AutoOpen()
        {
            try
            {
                // This must happen first for logging to work
                ExcelAsyncUtil.Initialize();
                _logger.Info("Registered Synchronization manager.");

                // Setup cortexes from config files
                _logger.Info("Creating container...");
                Container = ContainerServices.CreateContainer();
                var temp = Container.GetExportedValues<ExcelConfig>();
                Config = Container.GetExportedValue<ExcelConfig>();

                _logger.Info("Creating engine manager...");
                Engines = Container.GetExportedValue<EngineManager>();

                // Connect & Initialise Engines
                foreach (var link in Config.Links)
                {
                    if (String.IsNullOrEmpty(link.Alias)) continue;

                    _logger.Info("Connecting to Engine, Alias = {0}...", link.Alias);
                    if (!String.IsNullOrEmpty(link.Address))
                        Engines.Connect(link.Alias, link.Address);
                    else if (link.Alias.Equals(EmbeddedAlias))
                        Engines.Create(link.Alias);
                    else
                        Engines.CreateAppDomain(link.Alias);

                    if (Engines.Clients.Count() == 1 || link.IsDefault)
                        Engines.MakeDefault(link.Alias);
                }

                // Loading custom list of assemblies
                if (Config.Assemblies != null)
                    foreach (var asm in Config.Assemblies.Where(File.Exists))
                        Assembly.LoadFile(asm);

                // Read and execute scripts
                AutoRun();

                // Hook up exception logging
                _logger.Info("Registering custom exception handler...");
                ExcelIntegration.RegisterUnhandledExceptionHandler(ExceptionHandler);

                // Create the bind manager
                _logger.Info("Creating binding pipeline...");
                Binder = Container.GetExportedValue<BindManager>();

                // This must happen after creating the bind manager.
                _logger.Info("Registering on-load function-binder...");
                AppDomain.CurrentDomain.AssemblyLoad += AutoRegister;

                _logger.Info("Binding Cubicle functions of already loaded assemblies...");
                foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
                    AutoRegister(this, new AssemblyLoadEventArgs(asm));

                _logger.Info("Executing bind-scripts in AutoBind directory...");
                AutoBind();

                _logger.Info("Creating context menu items...");
                Container.GetExportedValue<ContextMenuFactory>();

                _logger.Info("CubicleAddIn: Finished AutoOpen.");
            }
            catch (Exception ex)
            {
                _logger.ErrorException("Critical failure starting Add-In", ex);
            }
        }

        public void AutoClose()
        {
            //Engines.Clients.Iter(x => if (x.Value is IDisposable) then x.Value.Dispose());
        }

        #endregion

        #region AutoRun

        private void AutoRun()
        {
            foreach (var script in AutoRunFiles)
            {
                try
                {
                    _logger.Info("Executing script. Path = {0}", script);
                    var engine = Engines.DefaultClient; //Engines.Get(script.TargetAlias);
                    var data = Helpers.ReadBytesFromFile(script);
                    engine.RunScript(data);
                }
                catch (Exception ex)
                {
                    _logger.ErrorException("Error executing script.", ex);
                }
            }
        }

        public string AutoRunPath
        {
            get { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _autoRunPath); }
        }

        public IEnumerable<string> AutoRunFiles
        {
            get
            {
                if (!Directory.Exists(AutoRunPath))
                {
                    _logger.Warn("Auto run path does not exist. Path = {0}", AutoRunPath);
                    return new List<string>();
                }
                return Directory.GetFiles(AutoRunPath).OrderBy(x => x);
            }
        }

        #endregion

        #region AutoBind

        private void AutoBind()
        {
            foreach (var script in AutoBindFiles)
            {
                _logger.Info("Executing bind script. Path = {0}", script);
                BindingFunctions.BindScript(script);
            }
        }

        public string AutoBindPath
        {
            get { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _autoBindPath); }
        }

        public IEnumerable<string> AutoBindFiles
        {
            get
            {
                if (!Directory.Exists(AutoBindPath))
                {
                    _logger.Warn("Auto bind path does not exist. Path = {0}", AutoBindPath);
                    return new List<string>();
                }
                return Directory.GetFiles(AutoBindPath).OrderBy(x => x);
            }
        }

        #endregion

        private void AutoRegister(object sender, AssemblyLoadEventArgs args)
        {
            // Skip this assembly
            if (args.LoadedAssembly.FullName == Assembly.GetExecutingAssembly().FullName) return;

            // Dynamic assemblies should not be processed
            // Worksheet function binding creates a dynamic assembly which will cause an infinite loop.
            if (args.LoadedAssembly.IsDynamic) return;

            if (Config.AutoRegisterFunctions)
            {
                CustomHelpers.AutoRegisterDnaMethods(args.LoadedAssembly, Config.RegisterExplicit);
                CustomHelpers.AutoRegisterCubicleMethods(args.LoadedAssembly);
            }
        }

        private static object ExceptionHandler(object exceptionobject)
        {
            _logger.ErrorException("Unexpected exception.", (Exception)exceptionobject);
            return ExcelError.ExcelErrorValue;
        }

    }
}
